"""In this module there are the needed classes and functions
for drawing a 3d treemap"""

from scopiavf.engine.rendertree import *
from scopiavf.engine.tools import *
import scopiavf.engine.interactive as interactive
from numpy import *

class Box(object):
    "A normal 2D box defined by two corners"
    def __init__(self, min, max):
        self.min = min
        self.max = max

    def split(self, cut):
        """Split the box in two so the left one has size "cut" and the
           right one 1.0 - cut, choosing the best axis for it."""
        minx, miny = self.min
        maxx, maxy = self.max
        width = maxx - minx
        height = maxy - miny
        wratio = min( width*cut / height, width*(1.0-cut) / height )
        if width > height and wratio > 0.5:
            left = Box( ( minx, miny ), (minx + width*cut, maxy) )
            right = Box( ( minx + width*cut, miny ), (maxx, maxy) )
        else:
            left = Box( ( minx, miny ), (maxx, miny + height*cut) )
            right = Box( ( minx, miny + height*cut ), (maxx, maxy) )
        return (left, right)

def findCut( areas ):
    """Given a list of areas/sizes, choose a split point so both size are
       as similar in size as possible. It returns the size of the left part
       and the cut position."""
    total = 0.0
    for a in areas: total += a
    a = None
    b = 0.0
    sum = 0.0
    cut = 0
    for area in areas:
        sum += area
        a = b
        b = sum / total
        cut += 1
        if a <= 0.5 and b > 0.5: break
    if a == None or a == 0.0: return (cut, b)
    if cut == 1: return (cut, b)
    if abs(0.5 - a) < abs(0.5 - b): return (cut-1, a)
    return (cut, b)

def distribute( elements, box=Box( (-0.5,-0.5), (0.5,0.5) ) ):
    """Distributes a list of sizes in boxes withit the given box.
       Returns a tuple with the resulting boxes.""" 
    if len(elements) == 1: return ( box, )
    p, cut = findCut( elements )
    eleft = elements[:p]
    eright = elements[p:]
    bleft, bright = box.split(cut)
    return distribute(eleft, bleft) + distribute(eright, bright)

class TreeBox(RenderGeomNode):
    """Render node for a tree box. Paints the area and the given text"""

    def __init__(self, context, style, text, min, max, callback = None):
        """Init function for the tree box
                context         context (glContext_t derivate) to work with.
                style           Style instance for drawing.
                text            Text to be drawn
                min, max        Corners of the box to paint
                callback        Callback to call in case it is clicked."""
        RenderGeomNode.__init__(self, context, style)
        self.min = min
        self.max = max
        self.center = array( ((min[0] + max[0])*0.5, (min[1] + max[1])*0.5, 0), float32 )
        self.xradius = (max[0] - min[0])*0.5
        self.yradius = (max[1] - min[1])*0.5
        self.text = text
        self.callback = callback
        self.styleProps = { 'color': (0.5, 0.75, 1.0), 'textcolor':(0.0, 0.0, 0.0) }
    
    def renderAll(self):
        if self.callback: self.context.startCallback( self.callback )
        self.style.drawNode( self.context, self )
        if self.callback: self.context.endCallback()
    
    def expandArea(self):
        "Returns the box which is available to place child boxes in this box"
        reserved = min(self.xradius, self.yradius)*0.15
        cx, cy, cz = self.center
        if self.xradius > self.yradius:
            return ( (-self.xradius+cx, -self.yradius+cy), (self.xradius-reserved+cx, self.yradius+cy) )
        else:
            return ( (-self.xradius+cx, -self.yradius+cy), (self.xradius+cx, self.yradius-reserved+cy) )
    
    def reservedArea(self):
        "Returns the box which is reserved for decorations (title) of this box"
        reserved = min(self.xradius, self.yradius)*0.15
        cx, cy, cz = self.center
        if self.xradius > self.yradius:
            return ( (self.xradius-reserved+cx, -self.yradius+cy), (self.xradius+cx, self.yradius+cy) )
        else:
            return ( (-self.xradius+cx, self.yradius-reserved+cy), (self.xradius+cx, self.yradius+cy) )
    

class TreeExtrusion(RenderGeomNode):
    """Render node to draw the extrusion body when a box is expandend"""

    def __init__(self, context, style, center, height, xradius, yradius, mindim, callback = None):
        """Init function
                center          Central point of the box
                height          Height to extrude the box
                xradius,yradius Size of the box in both axis
                mindim          Minimun dimensions of the elements in this box"""
        RenderGeomNode.__init__(self, context, style)
        self.center = center
        self.height = height
        self.xradius = xradius
        self.yradius = yradius
        self.mindim = mindim
        self.callback = callback
        self.visible = 0.0
    
    def renderAll(self):
        if self.callback: self.context.startCallback( self.callback )
        self.style.drawBranch( self.context, self )
        if self.callback: self.context.endCallback()

class Tree(RenderComposeNode):
    """Render node for the a whole tree node. Composes the box, sign, 
       extrusion and children if it is expanded."""

    def __init__(self, context, style, text, min, max, level = 1):
        """Init function
                text        Text to act as title
                min, max    Corners of the box
                level       depth of this node in the tree"""
        RenderComposeNode.__init__(self, context, style)
        self.isLeaf = False
        self.level = level
        self.showHooks = []
        self.nextDistance = 0.1/float(1<<level)
        self.center = array( ((min[0] + max[0])*0.5, (min[1] + max[1])*0.5, 0), float32 )
        self.text = text
        # box with title
        self.geom = TreeBox(context, style, text, min, max, Tree.MouseExpand(self) )
        visible = self.geom.visible = 0.0
        # transform to do effects with it
        self.geomT = RenderSimpleTransformNode(context, style, self.geom)
        self.geomT.set( self.center, (visible, visible, visible) )
        self.appendChild( self.geomT )
        self.styleProps = { 'color': (0.5, 0.75, 1.0), 'textcolor':(0.0, 0.0, 0.0) }
        self.geom.styleProps = self.styleProps
        self.expanded = False

    class MouseExpand(interactive.MouseHandler):
        button = 0
        def __init__( self,  tree):
            self.tree = tree
        def down(self, x, y): self.tree.expand()

    class MouseColapse(interactive.MouseHandler):
        button = 0
        def __init__( self,  tree):
            self.tree = tree
        def down(self, x, y): self.tree.colapse()

    class MouseFocus(interactive.MouseHandler):
        button = 0
        def __init__( self,  tree):
            self.tree = tree
        def down(self, x, y): self.tree.focus()

    def getPath(self):
        "Returns a list with the texts of all nodes from the root to this"
        path  = [ self.text ]
        node = self.parent
        while node:
            if isinstance(node, Tree): path = [ node.text ] + path
            node = node.parent
        return path

    def setStyle(self, vals):
        "Sets the style CSS properties for this node"
        self.styleProps.update(vals)
        self.geom.styleProps = self.styleProps

    def expandArea(self):
        "Proxy method to get the expand area"
        return self.geom.expandArea()
    
    def onShow(self):
        "Called when the node has finishing doing its appearing animation. Calls callbacks is showHooks"
        for f in self.showHooks: f()
        self.showHooks = []

    def show(self):
        "Creates a timer to animate the transform node of the box so it appears smoothly"
        geom = self.geom
        geomT = self.geomT
        # geometry goes live and that makes parent and the full path to be live
        geomT.goDynamic()
        def timer(elapsed):
            if geom.visible < 1.0: 
                geom.visible += elapsed
                if geom.visible > 1.0: geom.visible = 1.0
                geomT.set( self.center, (geom.visible, geom.visible, geom.visible) )
                return True
            else:
                # when we finish we go static and call onShow
                geomT.goStatic()
                self.onShow()
                return False
        self.context.addTimer( timer )

    def hide(self):
        "Creates a timer to make the element disappear by animating the transform"
        geom = self.geom
        geomT = self.geomT
        self.geomT.goDynamic()
        def timer(elapsed):
            if geom.visible > 0.0: 
                geom.visible -= elapsed
                if geom.visible < 0.0: geom.visible = 0.0
                geomT.set( self.center, (geom.visible, geom.visible, geom.visible) )
                return True
            else:
                self.geom.goStatic()
                return False
        self.context.addTimer( timer )

    def buildExpansion(self):
        """Builds the expansion of a node. Which is the extrusion body and the
           child treeboxes."""
        if self.isLeaf: return False
        children = self.getChildren()
        if not children: return False
        mindim = min( self.geom.xradius, self.geom.yradius )
        for c in children: 
            c.level = self.level + 1
            mindim = min(mindim, c.geom.xradius, c.geom.yradius )
        rmin, rmax = self.geom.reservedArea()
        # make a small box with the title so the name of this box is still visible
        # and give it a position and so
        sign = TreeBox(self.context, self.style, self.geom.text, rmin, rmax, Tree.MouseColapse(self) )
        signT = RenderSimpleTransformNode(self.context, self.style, sign)
        signT.set( sign.center, (0,0,0) )
        sign.styleProps = self.styleProps
        mindim = min(mindim, sign.xradius, sign.yradius )
        # create the extrusion body
        branch = TreeExtrusion(self.context, self.style, self.center, self.nextDistance, 
                                 self.geom.xradius, self.geom.yradius, mindim, Tree.MouseFocus(self) )
        branch.styleProps = self.styleProps
        self.branch = branch
        self.sign = sign
        self.signT = signT
        self.expanded = True
        # make a composite node with a transformation for anim
        self.compExpansion = RenderComposeNode(self.context, self.style)
        self.compExpansionT = RenderSimpleTransformNode(self.context, self.style, self.compExpansion)
        self.compExpansionT.scaleFirst = True
        # and add the extrusion, sign and children
        self.compExpansion.appendChild( branch )
        self.compExpansion.appendChild( signT )
        for c in children: self.compExpansion.appendChild( c )
        # finally connect everything to this node
        self.appendChild( self.compExpansionT )
        return True

    def expand(self, dofocus = True):
        "Expand this node and start the animation. If focus is true send the camera here"
        # create expansion geometry
        if not self.buildExpansion(): return self.select()
        self.expanded = True
        # call the children to show
        for c in self.compExpansion.children[2:]: c.show()
        compExpansionT = self.compExpansionT
        signT = self.signT
        sign = self.sign
        compExpansionT.expansion = 0.0
        # children are already live cause we called show on them
        # now we set extrusion and sign live too
        self.branch.goDynamic()
        signT.goDynamic()
        def timer(elapsed):
            if compExpansionT.expansion < 1.0: 
                compExpansionT.expansion += elapsed
                if compExpansionT.expansion > 1.0: compExpansionT.expansion = 1.0
                compExpansionT.set( (0, 0,self.nextDistance), (1.0, 1.0,compExpansionT.expansion) )
                signT.set( sign.center, (compExpansionT.expansion,compExpansionT.expansion,1.0) )
                self.branch.visible = compExpansionT.expansion
                return True
            else:
                self.branch.goStatic()
                signT.goStatic()
                return False
        self.context.addTimer( timer )
        if dofocus: self.focus()

    def colapse( self ):
        "Close and destroy the expansion of this node. First, start the closing anim."
        self.expanded = False
        for c in self.compExpansion.children[2:]: 
            c.hide()
            if c.expanded: c.colapse()
        compExpansionT = self.compExpansionT
        signT = self.signT
        sign = self.sign
        self.branch.goDynamic()
        signT.goDynamic()
        def timer(elapsed):
            if compExpansionT.expansion > 0.0: 
                compExpansionT.expansion -= elapsed
                if compExpansionT.expansion < 0.0: compExpansionT.expansion = 0.0
                compExpansionT.set( (0, 0,self.nextDistance), (1.0, 1.0,compExpansionT.expansion) )
                signT.set( sign.center, (compExpansionT.expansion,compExpansionT.expansion,1.0) )
                self.branch.visible = compExpansionT.expansion
                return True
            else:
                self.branch.goStatic()
                signT.goStatic()
                # anim donde, destroy everything
                self.removeChild( self.compExpansionT )
                del self.compExpansionT
                del self.compExpansion
                del self.branch
                del self.sign
                del self.signT
                return False
        self.context.addTimer( timer )

    def getExpandedChilds(self):
        "Returns the actual children of this node excluding decorations"
        if self.expanded:
            return self.compExpansion.children[2:]
        else: return []

    def select(self):
        "Starts the animation of highlighting the box for two seconds"
        geom = self.geom
        try: 
            h = geom.highlight
            return
        except: pass
        geom.goDynamic()
        color = array(geom.styleProps['color'], float32 )
        fadeto = array( (1,1,1), float32 )
        geom.highlight = 2.0
        def timer(elapsed):
            if geom.highlight > 1.0: elapsed *= 5.0
            if geom.highlight > 0.0: 
                geom.highlight -= elapsed
                if geom.highlight < 0.0: geom.highlight = 0.0
                f = geom.highlight
                if f > 1.0: f = 2.0 - f
                of = 1.0 - f
                geom.styleProps['color'] = tuple((fadeto * f + color * of).tolist())
                return True
            else:
                geom.goStatic()
                geom.styleProps['color'] = tuple(color.tolist())
                del geom.highlight
                return False
        self.context.addTimer( timer )
        #self.context.onSelect(self.getPath(), self)

    def focus(self):
        "Make the camera focus on this element by calling the context"
        accZ = 0.0
        for i in xrange(self.level): accZ += 0.1/float(1<<(i+1))
        self.context.centerView( self.center + array((0,0,accZ),float32), 
                max(self.geom.xradius, self.geom.yradius)*2 )
